// 采用类的方式进行封装会让代码更具有复用性,也方便添加新的属性和方法
// 暴露封装好的wx.request类
export default class WxRequest {
  // 定义实例属性用来设置默认请求参数
  defaults = {
    baseURL: '', // 请求基础路径
    url: '', // 每个接口的请求路径会和基础路径进行拼接
    data: null, // 请求参数
    method: 'GET', // 默认请求方法
    header: {
      // 设置请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000, // 小程序默认的请求超时时间 1分钟
    isLoading: true // 控制是否使用默认的loading
  }
  // 定义拦截器对象(默认请求拦截器 + 默认响应拦截器)
  interceptors = {
    // 默认请求拦截器：在请求发送前对请求参数进行新增或修改
    request: (config) => config, // 注意：一定要返回请求参数对象,
    // 默认响应拦截器：对服务器返回的数据进行处理
    response: (response) => response
  }
  // 定义数组队列
  // 初始值需要是一个空数组，用存储请求队列，存储请求标识
  queue = []
  // 用于创建和初始化类的属性
  constructor(params = {}) {
    // 将传递的参数覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }
  // 请求方法
  // request方法接收一个对象类型的参数，属性值和 wx.request 方法调用传递的参数保持一致
  request(options) {
    // 如果有新的请求，就清除上一次的定时器
    this.timeId && clearTimeout(this.timeId)
    // 注意：需要先将基础路径和传入的url进行合并
    options.url = this.defaults.baseURL + options.url
    // 合并请求参数
    options = { ...this.defaults, ...options }
    // 在请求发送之前，开启加载效果
    if (options.isLoading && options.method != 'UPLOAD') {
      // wx.upload自带加载效果
      // 判断 queue 队列是否为空，如果为空显示loading，如果不为空则不显示loading
      this.queue.length === 0 &&
        wx.showLoading({
          title: '加载中...'
        })
      // 然后向queue数组队列中添加请求标识
      this.queue.push('request')
    }
    // 在请求发送之前调用请求拦截器,对请求参数进行新增，修改
    options = this.interceptors.request(options)
    // 需要使用 Promise 封装 wx.request 处理异步请求
    return new Promise((resolve, reject) => {
      if (options.method === 'UPLOAD') {
        wx.uploadFile({
          ...options,
          success: (res) => {
            // 1.对服务器返回的JSON字符串转成对象
            res.data = JSON.parse(res.data)
            // 2.合并参数传递给响应拦截器
            const mergeRes = Object.assign({}, res, {
              config: options,
              isSuccess: true
            })
            // 3.将响应拦截器返回的数据返回
            resolve(this.interceptors.response(mergeRes))
          },
          fail: (err) => {
            // 1.合并参数传递给响应拦截器
            const mergeRes = Object.assign({}, err, {
              config: options,
              isSuccess: true
            })
            // 2.将响应拦截器返回的数据返回
            reject(this.interceptors.response(mergeRes))
          }
        })
      } else {
        wx.request({
          ...options, // 将接收到的参数进行展开，作为请求方法的参数
          // 当接口调用成功时会触发success回调函数
          success: (res) => {
            // 不管是成功响应还是失败响应，都需要调用响应拦截器
            // 响应拦截器需要接收服务器响应的数据对数据进行处理，再进行返回
            // 在给响应拦截器传递参数时，需要将请求参数也一起传递，方便进行代码的调试和其他逻辑处理，需要进行参数合并
            // isSuccess属性用于判断响应是否成功
            // 调用响应拦截器
            res = this.interceptors.response(
              Object.assign({}, res, { config: options, isSuccess: true })
            )
            resolve(res) // 返回请求成功的数据
          },
          // 当接口调用失败时会触发fail回调函数(出现网络错误)
          fail: (err) => {
            // 调用响应拦截器
            err = this.interceptors.response(
              Object.assign({}, err, { config: options, isSuccess: false })
            )
            reject(err) // 返回错误信息
          },
          // 无论接口调用成功或失败都会执行
          complete: () => {
            if (options.isLoading) {
              // 每请求完成一次就删除一个标识
              this.queue.pop()
              this.queue.length === 0 && this.queue.push('request')
              // 等100ms若没有新的请求发起则清空quene关闭加载效果，此时若有新的请求发起queue不为空则不会出现新的加载效果
              this.timeId = setTimeout(() => {
                // 若在100ms内无新的请求进来则关闭加载效果
                this.queue.pop()
                // 判断queue队列是否为空，当queue为空时关闭加载效果
                this.queue.length === 0 && wx.hideLoading()
                clearTimeout(this.timeId)
              }, 100)
            }
          }
        })
      }
    })
  }
  // 封装GET实例方法
  get(url, data = {}, config = {}) {
    // url:请求地址 data:请求数据 config:其他配置项
    // 本质还是调用了request方法，只是将请求方法固定为GET，将调用request方法的返回值通过return返回
    return this.request(Object.assign({ url, data, method: 'GET' }, config))
  }
  // 封装POST实例方法
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'POST' }, config))
  }
  // 封装DELETE实例方法
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'DELETE' }, config))
  }
  // 封装PUT实例方法
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'PUT' }, config))
  }
  // 封装ALL实例方法用于处理并发请求
  all(promise) {
    return Promise.all(promise)
  }
  // upload实例方法，用来对wx.uploadFile进行封装
  upload(url, filePath, name = 'file', config = {}) {
    return this.request(
      Object.assign({ url, filePath, name, method: 'UPLOAD' }, config)
    )
  }
}
